package fregat.connector.base;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import fregat.connector.ConnectorService;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import fregat.base.util.ConfigurationBundle;

/**
 * Helper class that wraps the org.apache.commons.net FTP client to move files from and to
 * a FTP server.
 */
public class FTPTransfer {
  private String server;
  private String user;
  private String password;
  private String directory;
  private FTPClient ftp;
  private String name;

  /**
   * Configures the FTP transfer by reading the connection parameters from the configuration file.
   *
   * @param configPath Path to channel's configuration
   */
  public FTPTransfer(String configPath) {
    ConfigurationBundle configuration = ConnectorService.getConfiguration();
    server = configuration.getConfigurationString(configPath + ".ftp.server");
    user = configuration.getConfigurationString(configPath + ".ftp.user");
    password = configuration.getConfigurationString(configPath + ".ftp.password");
    directory = configuration.getConfigurationString(configPath + ".ftp.directory");


    if (isConfigured()) {
      ftp = new FTPClient();

      if (directory.endsWith("/")) {
        directory = directory.substring(0, directory.length() - 1);
      }
      name = "ftp://" + server + directory;

      if (!connect()) {
        ConnectorService.error(ConnectorService.resource.getString("FTPTransfer_constructor_1").replaceFirst("%server", toString()));
        System.exit(-1);
      } else {
        try {
          ftp.logout();
        } catch (IOException ex) {
        }
      }
    }
  }

  /**
   * @return True, if FTP transfer is configured
   */
  public boolean isConfigured() {
    return server != null;
  }

  /**
   * Checks whether the given file exists on the FTP server. If the connection
   * fails false is returned.
   *
   * @param file Description of the file
   * @return True, if file exists
   */
  public synchronized boolean fileExists(FileDescription file) {
    if (connect()) {
      try {
        boolean fileExists = exists(file);
        ftp.logout();
        return fileExists;
      } catch (IOException ex) {
        ConnectorService.error("FTPTransfer.fileExists", ex);
      }
    }
    return false;
  }

  /**
   * Searches the files existing on the FTP server for matching
   * the given one.
   *
   * @param file Description of the file
   * @return True, if file matches
   * @throws IOException
   */
  private boolean exists(FileDescription file) throws IOException {
    String names[] = ftp.listNames(directory);
    checkReply("FTPTransfer_exists_", file, false);

    int i = 0;
    // RMA20060711: replace \ weil bei Windows NT-FTP /ECOLOG\VOLLZUG.CTL bei
    // listNames retouniert wird
    while (i < names.length && !names[i].replaceAll("\\\\","/").equalsIgnoreCase(directory + "/" + file.getName())) {
        i++;
    }

    return i != names.length;
  }

  /**
   * Moves the given local file to the FTP server.
   *
   * @param file Description of the file
   */
  public synchronized void storeFile(FileDescription file) {
    file.setTransferred(false);
    if (connect()) {
      try {
        FileInputStream local = new FileInputStream(file.toString());
        ftp.storeFile(directory + "/" + file.getName(), local);
        local.close();
        file.setTransferred(checkReply("FTPTransfer_storeFile_", file, true));
        ftp.logout();
      } catch (IOException ex) {
        ConnectorService.error("FTPTransfer.storeFile", ex);
      }
    }
  }

  /**
   * Moves the given file from to FTP server to the local directory.
   *
   * @param file Description of the file
   */
  public synchronized void retrieveFile(FileDescription file) {
    file.setTransferred(false);
    if (connect()) {
      try {
        if (exists(file)) {
          FileOutputStream local = new FileOutputStream(file.toString());
          ftp.retrieveFile(directory + "/" + file.getName(), local);
          local.close();
          file.setTransferred(checkReply("FTPTransfer_retrieveFile_", file, true));
        }

        ftp.logout();
      } catch (IOException ex) {
        ConnectorService.error("FTPTransfer.retrieveFile", ex);
      }
    }
  }

  /**
   * Deletes the given file on the FTP server. If the connection
   * fails false is returned.
   *
   * @param file Description of the file
   * @return True, if deleted
   */
  public synchronized boolean deleteFile(FileDescription file) {
    boolean deleted = false;
    if (connect()) {
      try {
        deleted = ftp.deleteFile(directory + "/" + file.getName());
        checkReply("FTPTransfer_deleteFile_", file, true);
        ftp.logout();
      } catch (IOException ex) {
        ConnectorService.error("FTPTransfer.deleteFile", ex);
      }
    }
    return deleted;
  }

  /**
   * Connects to the FTP server using the configured values.
   *
   * @return True, if connection established
   */
  private boolean connect() {
    try {
      ftp.connect(server);
      if (checkReply("FTPTransfer_connect_", null, false)) {
        ftp.enterLocalPassiveMode();
        if (ftp.login(user, password)) {
          return true;
        }
        checkReply("FTPTransfer_login_", null, false);
      }
    } catch (IOException ex) {
      ConnectorService.error("FTPTransfer.connect", ex);
    }
    return false;
  }

  /**
   * Checks the reply code of the last operation and logs the message.
   *
   * @param key         Resource key for the debug message
   * @param file        Description of the file
   * @param logPositive True, if positive result should be logged also
   * @return True, if positive reply
   */
  private boolean checkReply(String key, FileDescription file, boolean logPositive) {
    int reply = ftp.getReplyCode();
    boolean positive = FTPReply.isPositiveCompletion(reply);
    String log = ConnectorService.resource.getString(key + (positive && logPositive ? "2" : "1")).replaceFirst("%server", toString());
    if (file != null) {
      log = log.replaceFirst("%file", file.getName()).replaceFirst("%dir", file.getDirectory().length() > 0 ? file.getDirectory() : ".\\" + File.separator);
    }

    if (positive && logPositive) {
      ConnectorService.debug(log);
    }

    if (!positive) {
      ConnectorService.error(log + " (" + reply + ")");
    }

    return positive;
  }

  public String toString() {
    return name;
  }
}
